from django.db import models, transaction
from django.db.models import F
from django.utils import timezone
from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _

from ...core.conf import settings
from ...core.utils import get_query_string
from .managers import (
    CommentPollChoiceQuerySet,
    CommentPollQuerySet,
    CommentPollVoteQuerySet,
)


class PollMode:
    DEFAULT, SECRET = range(2)
    LIST = ((DEFAULT, "default"), (SECRET, "secret"))
    BY_ID = dict(LIST)
    BY_NAME = {name: id_ for id_, name in LIST}


class CommentPoll(models.Model):
    comment = models.ForeignKey(
        "spirit_comment.Comment", related_name="comment_polls", on_delete=models.CASCADE
    )

    name = models.CharField(_("name"), max_length=255)
    title = models.CharField(_("title"), max_length=255, blank=True)
    choice_min = models.PositiveIntegerField(_("choice min"), default=1)
    choice_max = models.PositiveIntegerField(_("choice max"), default=1)
    mode = models.IntegerField(
        _("mode"), choices=PollMode.LIST, default=PollMode.DEFAULT
    )
    close_at = models.DateTimeField(_("auto close at"), null=True, blank=True)
    is_removed = models.BooleanField(default=False)
    created_at = models.DateTimeField(default=timezone.now)

    objects = CommentPollQuerySet.as_manager()

    class Meta:
        unique_together = ("comment", "name")
        verbose_name = _("comment poll")
        verbose_name_plural = _("comments polls")

    def get_absolute_url(self):
        return self.comment.get_absolute_url()

    def get_rel_url(self, request):
        # todo: remove
        query_string = get_query_string(request, show_poll=0)
        return "".join((request.path, "?", query_string, "#p", str(self.pk)))

    @property
    def is_multiple_choice(self):
        return self.choice_max > 1

    @property
    def has_choice_min(self):
        return self.choice_min > 1

    @property
    def is_closed(self):
        return self.close_at and self.close_at <= timezone.now()

    @property
    def is_secret(self):
        return self.mode == PollMode.SECRET

    @property
    def can_show_results(self):
        return not self.is_secret or self.is_closed

    @property
    def mode_txt(self):
        return PollMode.BY_ID[self.mode]

    @cached_property
    def has_user_voted(self):
        # *choices* is dynamically created by comments.with_polls()
        try:
            return any(c.vote for c in self.choices)
        except AttributeError:
            return

    @cached_property
    def total_votes(self):
        # *choices* is dynamically created by comments.with_polls()
        try:
            return sum(c.vote_count for c in self.choices)
        except AttributeError:
            return

    @classmethod
    def update_or_create_many(cls, comment, polls_raw):
        cls.objects.for_comment(comment).update(is_removed=True)

        default_fields = ["title", "choice_min", "choice_max", "close_at", "mode"]

        if not polls_raw:  # Avoid the later transaction.atomic()
            return

        with transaction.atomic():  # Speedup
            for poll in polls_raw:
                defaults = {
                    field: poll[field] for field in default_fields if field in poll
                }
                defaults.update({"is_removed": False})

                cls.objects.update_or_create(
                    comment=comment, name=poll["name"], defaults=defaults
                )


class CommentPollChoice(models.Model):
    poll = models.ForeignKey(
        CommentPoll, related_name="poll_choices", on_delete=models.CASCADE
    )

    number = models.PositiveIntegerField(_("number"))
    description = models.CharField(_("choice description"), max_length=255)
    vote_count = models.PositiveIntegerField(_("vote count"), default=0)
    is_removed = models.BooleanField(default=False)

    objects = CommentPollChoiceQuerySet.as_manager()

    class Meta:
        unique_together = ("poll", "number")
        ordering = ["number"]
        verbose_name = _("poll choice")
        verbose_name_plural = _("poll choices")

    @property
    def vote(self):
        # *votes* is dynamically created by comments.with_polls()
        try:
            assert len(self.votes) <= 1, "Panic, too many votes"
            return self.votes[0]
        except (AttributeError, IndexError):
            return

    @property
    def votes_percentage(self):
        try:
            return (self.vote_count / self.poll.total_votes) * 100
        except ZeroDivisionError:
            return 0

    @classmethod
    def increase_vote_count(cls, poll, voter):
        cls.objects.for_vote(poll=poll, voter=voter).update(
            vote_count=F("vote_count") + 1
        )

    @classmethod
    def decrease_vote_count(cls, poll, voter):
        cls.objects.for_vote(poll=poll, voter=voter).update(
            vote_count=F("vote_count") - 1
        )

    @classmethod
    def update_or_create_many(cls, comment, choices_raw):
        cls.objects.for_comment(comment).update(is_removed=True)

        if not choices_raw:  # Avoid the later transaction.atomic()
            return

        poll_ids_by_name = dict(
            CommentPoll.objects.for_comment(comment)
            .unremoved()
            .values_list("name", "id")
        )

        with transaction.atomic():  # Speedup
            for choice in choices_raw:
                cls.objects.update_or_create(
                    poll_id=poll_ids_by_name[choice["poll_name"]],
                    number=choice["number"],
                    defaults={
                        "description": choice["description"],
                        "is_removed": False,
                    },
                )


class CommentPollVote(models.Model):
    voter = models.ForeignKey(
        settings.AUTH_USER_MODEL, related_name="st_cp_votes", on_delete=models.CASCADE
    )
    choice = models.ForeignKey(
        CommentPollChoice, related_name="choice_votes", on_delete=models.CASCADE
    )

    is_removed = models.BooleanField(default=False)
    created_at = models.DateTimeField(default=timezone.now)

    objects = CommentPollVoteQuerySet.as_manager()

    class Meta:
        unique_together = ("voter", "choice")
        ordering = ["-pk"]
        verbose_name = _("poll vote")
        verbose_name_plural = _("poll votes")
